package com.sixbro.shiro.session.config.shiro;

import com.bxs.rbac.shiro.session.constant.Constant;
import com.bxs.rbac.shiro.session.domain.entity.Authority;
import com.bxs.rbac.shiro.session.domain.entity.User;
import com.bxs.rbac.shiro.session.service.UserService;
import org.apache.shiro.authc.AuthenticationException;
import org.apache.shiro.authz.AuthorizationInfo;
import org.apache.shiro.authz.SimpleAuthorizationInfo;
import org.apache.shiro.realm.AuthorizingRealm;
import org.apache.shiro.subject.PrincipalCollection;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.util.CollectionUtils;

import javax.annotation.Resource;
import java.util.concurrent.TimeUnit;

/**
 * <p>
 * 身份校验核心类
 * Realm是一个Dao，通过它来验证用户身份和权限。这里Shiro不做权限的管理工作，需要我们自己管理用户权限，只需要从我们的数据源中把用户和用户的角色权限信息取出来交给Shiro即可。Shiro就会自动的进行权限判断。
 * </p>
 *
 * @Author: Mr.Lu
 * @Since: 2020/6/17 15:37
 */
public class ShiroRealm extends AuthorizingRealm {
    private static Logger _logger = LoggerFactory.getLogger(ShiroRealm.class);

    private static final Long EXPIRATION_TIME = 24*60*60*1000L;

    // 这里尝试过使用@Autowired 但是发现会报错。这个是spring的注解。如果有知道原因的可以留言。谢谢
    @Autowired
    private UserService userService;
    @Resource
    private RedisTemplate redisTemplate;

    /**
     * 配置权限　注入权限
     * @param principals
     * @return
     */
    @Override
    protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals) {
        _logger.info("------------ 权限配置 ----------");

        User user = (User)principals.getPrimaryPrincipal();
        String key = Constant.REDIS_AUTHC_USER_INFO + user.getId();


        if (redisTemplate.hasKey(key)) {
            // redis 获取用户信息
            user = (User) redisTemplate.opsForValue().get(key);
        } else {
            // 注入角色(查询所有的角色注入控制器)
            user = userService.findUserByUserId(user.getId());

            //redis 缓存用户信息
            redisTemplate.opsForValue().set( key, user, 1, TimeUnit.DAYS);
        }

        if (CollectionUtils.isEmpty( user.getAuthorities() )) {
            return null;
        }

        SimpleAuthorizationInfo authorizationInfo = new SimpleAuthorizationInfo();
        for (Authority authority : user.getAuthorities()) {
            authorizationInfo.addStringPermission(authority.getEname());
        }

        return authorizationInfo;
    }

    /**
     * 用户验证
     * @param token 账户数据
     * @return
     * @throws AuthenticationException 根据账户数据查询账户。根据账户状态抛出对应的异常
     */
    @Override
    protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException {
        _logger.info("----------- 用户认证 ----------");

        //获取用户的输入的账号
        String username = (String) token.getPrincipal();
        //这里需注意。看别人的教程有人是这样写的String password = (String) token.getCredentials();
        //项目运行的时候报错，发现密码不正确。后来进源码查看发现将密码注入后。Shiro会进行转义将字符串转换成字符数组。
        //源码：this(username, password != null ? password.toCharArray() : null, false, null);
        //不晓得是否是因为版本的原因，建议使用的时候下载源码进行查看
        //String password = new String((char[]) token.getCredentials());
        //通过username从数据库中查找 User对象，如果找到，没找到.
        //实际项目中，这里可以根据实际情况做缓存，如果不做，Shiro自己也是有时间间隔机制，2分钟内不会重复执行该方法

        User user = userService.findUserByUsername(username);
        if (user == null) {
            throw new UnknownAccountException("用户不存在!");
        }

        switch ( user.getStatus() ) {
            case LOCKED:
                throw new LockedAccountException();
            case Disabled:
                throw new DisabledAccountException();
            default:
        }

        SimpleAuthenticationInfo authorizationInfo = new SimpleAuthenticationInfo(user,user.getPassword().toCharArray(),getName());
        return authorizationInfo;
    }
}
